//
//  IndexedModel.m
//  Renderbunny
//
//  Created by Holmes Futrell on 1/21/11.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import "IndexedModel.h"
#import "vector.h"
#import "rb_error_checking.h"

#define MAX_VERTS (65536)
#define MAX_TRIS  (3 * MAX_VERTS)

@implementation IndexedModel

typedef struct {
	int i1, i2, i3;
} indexTriplet;

unsigned int hashfunction(void *v) {
	indexTriplet *i = (indexTriplet *)v;
	return ((i->i1 * 2342334) % 34397) ^ ((i->i1 * 65455) % 25183) ^ ((i->i1 * 27654) % 20717);
}

int key_eq_fn(void* v1,void* v2) {
	indexTriplet *key1 = (indexTriplet *)v1;
	indexTriplet *key2 = (indexTriplet *)v2;
	return ((key1->i1 == key2->i1) && (key1->i2 == key2->i2) && (key1->i3 == key2->i3));
}

-(void)addIndexedTriangle:(int)i1 :(int)i2 :(int)i3 {
	indices[num_indices++] = i1;
	indices[num_indices++] = i2;
	indices[num_indices++] = i3;	
}

/*
	Given vertex information along with a set of indices, adds a new vertex to the list of vertices
	inserts this vertex into the hash table so that the new vertex index can be located by individual indexes
 */
 
-(int)addVertexWithIndividualIndices:(int)i1 :(int)i2 :(int)i3 {

	int index = num_indexed_vertices;
	
	num_indexed_vertices++;
	
	indexTriplet *key = malloc(sizeof(indexTriplet));;
	key->i1 = i1;
	key->i2 = i2;
	key->i3 = i3;
	
	
	int *value = malloc(sizeof(int));
	*value = index;
		
	hashtable_insert( hTable, key, value );

	return index;
	
}

-(BOOL)lookupVertex:(int)i1 :(int)i2 :(int)i3 :(int *)value {

	// lookup in hash table here
	
	indexTriplet key;
	key.i1 = i1;
	key.i2 = i2;
	key.i3 = i3;

	int *found = NULL;
		
	found = (int *)hashtable_search(hTable, &key);
	
	if (found) {
		*value = *found;
		return YES;
	}
	else {
		return NO;
	}
	
}

-(id)initWithFile:(NSString *)path {
	
	self = [super init];
	
	int i;
	for (i=0; i<IMODEL_NUM_BUFFERS; i++) {
		attribMapping[i] = -1;
	}
	
	hTable = create_hashtable(1000, hashfunction, key_eq_fn);
		
	char cPath[1024];
	[path getFileSystemRepresentation: cPath maxLength: 1024];
		
	
	indices = (GLushort *)malloc(MAX_TRIS * sizeof(GLshort));
	
	num_indexed_vertices = 0;
	num_indices = 0;
	
	int num_normals = 0;
	int num_vertices = 0;
	int num_tex_coords = 0;
	
	vec3 *temp_vertices							= (vec3 *)malloc(sizeof(vec3) * MAX_VERTS);
	vec3 *temp_normals							= (vec3 *)malloc(sizeof(vec3) * MAX_VERTS);
	vec2 *temp_texcoords						= (vec2 *)malloc(sizeof(vec2) * MAX_VERTS);
	
	vec3 *indexed_vertices						= (vec3 *)malloc(sizeof(vec3) * MAX_VERTS);
	vec3 *indexed_normals						= (vec3 *)malloc(sizeof(vec3) * MAX_VERTS);
	vec2 *indexed_texcoords						= (vec2 *)malloc(sizeof(vec2) * MAX_VERTS);
	vec3 *indexed_tangents						= (vec3 *)malloc(sizeof(vec3) * MAX_VERTS);
	
	for (i=0; i<MAX_VERTS; i++) {
		indexed_tangents[i] = make_vec3(0.0, 0.0, 0.0);
	}
	
	FILE *f = fopen(cPath, "r");
	
	if (f == NULL) {
		NSLog(@"Couldn't open model %@!", path);
		return NULL;
	}
	else {
		NSLog(@"loading model %@", path);
	}
	
	char line[1024];
	
	while(fgets(line, 1024, f) != NULL) {
		
		vec3 tempv3;
		vec2 tempv2;
		triangle tempt;
		char temps[128];
		
		if (sscanf(line, "v %f %f %f", &tempv3.coords[0], &tempv3.coords[1], &tempv3.coords[2]) == 3) {
			temp_vertices[num_vertices++] = make_vec3(tempv3.coords[0], tempv3.coords[1], tempv3.coords[2]);
		}
		else if (sscanf(line, "vt %f %f", &tempv2.coords[0], &tempv2.coords[1]) == 2) {
			temp_texcoords[num_tex_coords++] = tempv2;
		}
		else if (sscanf(line, "vn %f %f %f", &tempv3.coords[0], &tempv3.coords[1], &tempv3.coords[2]) == 3) {
			temp_normals[num_normals++] = normalize_vec3(tempv3);
		} // quads
		else if (sscanf(line, "f %d/%d/%d %d/%d/%d %d/%d/%d", \
						&tempt.v1, &tempt.t1, &tempt.n1, &tempt.v2, &tempt.t2, &tempt.n2, &tempt.v3, &tempt.t3, &tempt.n3) == 9) {
			
			int i1=0, i2=0, i3=0;
			
			if (![self lookupVertex: tempt.v1 :tempt.t1 :tempt.n1 :&i1]) {
				
				i1 = [self addVertexWithIndividualIndices: tempt.v1 : tempt.t1 : tempt.n1 ];
				
				indexed_vertices[i1] = temp_vertices[tempt.v1-1];
				indexed_normals[i1] = temp_normals[tempt.n1-1];
				indexed_texcoords[i1] = temp_texcoords[tempt.t1-1];				
				
				
				
			}

			if (![self lookupVertex: tempt.v2 :tempt.t2 :tempt.n2 :&i2]) {
				i2 = [self addVertexWithIndividualIndices: tempt.v2 : tempt.t2 : tempt.n2 ];
				
				indexed_vertices[i2] = temp_vertices[tempt.v2-1];
				indexed_normals[i2] = temp_normals[tempt.n2-1];
				indexed_texcoords[i2] = temp_texcoords[tempt.t2-1];				
			
			}
			
			if (![self lookupVertex: tempt.v3 :tempt.t3 :tempt.n3 :&i3]) {
				i3 = [self addVertexWithIndividualIndices: tempt.v3 : tempt.t3 : tempt.n3 ];
				
				indexed_vertices[i3] = temp_vertices[tempt.v3-1];
				indexed_normals[i3] = temp_normals[tempt.n3-1];
				indexed_texcoords[i3] = temp_texcoords[tempt.t3-1];				
				
			}
			
			[self addIndexedTriangle: i1 : i2 : i3];
			
			vec3 Q1 = difference_vec3(indexed_vertices[i2] , indexed_vertices[i1]);
			vec3 Q2 = difference_vec3(indexed_vertices[i3] , indexed_vertices[i1]);
			float t1 = indexed_texcoords[i2].coords[1] - indexed_texcoords[i1].coords[1];
			float t2 = indexed_texcoords[i3].coords[1] - indexed_texcoords[i1].coords[1];

			vec3 tangent = make_vec3(t2 * Q1.coords[0] - t1 * Q2.coords[0],
								t2 * Q1.coords[1] - t1 * Q2.coords[1],
								t2 * Q1.coords[2] - t1 * Q2.coords[2]);
			
			indexed_tangents[i1] = add_vec3(tangent, indexed_tangents[i1]);
			indexed_tangents[i2] = add_vec3(tangent, indexed_tangents[i2]);
			indexed_tangents[i3] = add_vec3(tangent, indexed_tangents[i3]);

		}
		else if (sscanf(line, "usemtl %s", temps) == 1) {
			continue;
		}
		else if (line[0] == '#' || line[0] == '\n') {
			continue;
		}
		else if (sscanf(line, "mtllib %s", temps) == 1) {
			continue;
		}
		else if (sscanf(line, "g %s", temps) == 1) {
			continue;
		}
		else {
			printf("I don't know what to do with %s", line);
		}
		
	}
	
	fclose(f);

	/*
	  normalize tangent vectors
	 */
	for (i=0; i<num_indexed_vertices; i++) {
		indexed_tangents[i] = normalize_vec3(indexed_tangents[i]);
	}
		
	/*
	 the .obj file starts with index 1 instead of 0 so we add padding
	 */
		
	printf("Vertices = %d\n", num_vertices);
	printf("Tex coords = %d\n", num_tex_coords);
	printf("Num Normals = %d\n", num_normals);
	
	printf("Indexed Vertices = %d\n", num_indexed_vertices);
	printf("Num Triangles = %d\n", num_indices / 3);

	
	glGenBuffers(IMODEL_NUM_BUFFERS, buffers);
	
	// generate the buffers
	glBindBuffer(GL_ARRAY_BUFFER, buffers[IMODEL_BUFFER_NORMAL]);
	glBufferData(GL_ARRAY_BUFFER, 3 * num_indexed_vertices * sizeof(GLfloat), indexed_normals, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, buffers[IMODEL_BUFFER_UV]);
	glBufferData(GL_ARRAY_BUFFER, 2 * num_indexed_vertices * sizeof(GLfloat), indexed_texcoords, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, buffers[IMODEL_BUFFER_VERTEX]);
	glBufferData(GL_ARRAY_BUFFER, 3 * num_indexed_vertices * sizeof(GLfloat), indexed_vertices, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, buffers[IMODEL_BUFFER_TANGENT]);
	glBufferData(GL_ARRAY_BUFFER, 3 * num_indexed_vertices * sizeof(GLfloat), indexed_tangents, GL_STATIC_DRAW);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[IMODEL_BUFFER_ELEMENTS]);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, num_indices * sizeof(GLushort), indices, GL_STATIC_DRAW);

	
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	
	/* delete temporary storage */
	free(temp_vertices);
	free(temp_texcoords);
	free(temp_normals);
	
	free(indexed_normals);
	free(indexed_texcoords);
	free(indexed_vertices);
	free(indexed_tangents);

	free(indices);
	indices = nil;
	
	hashtable_destroy(hTable, 1);
	hTable = nil;
	
	return self;
	
}

-(void)mapBuffer:(int)buffer toAttributeLocation:(GLuint)location {

	if ( buffer < 0 || buffer >= IMODEL_NUM_BUFFERS) {
		NSLog(@"Invalid buffer!");
		return;
	}
	
	attribMapping[buffer] = location;
	
}

-(void)createVertexArray {
	
	if (vao == 0) {
		// generate the vertex array object
#ifdef GL_ES_VERSION_2_0
		glGenVertexArraysOES(1,&vao);
#else
		glGenVertexArraysAPPLE(1,&vao);
#endif 
	}

#ifdef GL_ES_VERSION_2_0	
	glBindVertexArrayAPPLE(vao);
#else 
	glBindVertexArrayAPPLE(vao);
#endif
		
	// set up vertices
	if (attribMapping[IMODEL_BUFFER_VERTEX] != -1) {
		glBindBuffer(GL_ARRAY_BUFFER, buffers[IMODEL_BUFFER_VERTEX]);	
		glEnableVertexAttribArray(attribMapping[IMODEL_BUFFER_VERTEX]);	
		glVertexAttribPointer(attribMapping[IMODEL_BUFFER_VERTEX], 3, GL_FLOAT, 0, 0, 0);
	}
	
	// set up normals
	if (attribMapping[IMODEL_BUFFER_NORMAL] != -1) {
		glBindBuffer(GL_ARRAY_BUFFER, buffers[IMODEL_BUFFER_NORMAL]);	
		glEnableVertexAttribArray(attribMapping[IMODEL_BUFFER_NORMAL]);	
		glVertexAttribPointer(attribMapping[IMODEL_BUFFER_NORMAL], 3, GL_FLOAT, 0, 0, 0);
	}
	
	// set up tex coords
	if (attribMapping[IMODEL_BUFFER_UV] != -1) {
		glBindBuffer(GL_ARRAY_BUFFER, buffers[IMODEL_BUFFER_UV]);		
		glEnableVertexAttribArray(attribMapping[IMODEL_BUFFER_UV]);
		glVertexAttribPointer(attribMapping[IMODEL_BUFFER_UV], 2, GL_FLOAT, 0, 0, 0);	
	}
	
	// set up tex tangents
	if (attribMapping[IMODEL_BUFFER_TANGENT] != -1) {
		glBindBuffer(GL_ARRAY_BUFFER, buffers[IMODEL_BUFFER_TANGENT]);		
		glEnableVertexAttribArray(attribMapping[IMODEL_BUFFER_TANGENT]);
		glVertexAttribPointer(attribMapping[IMODEL_BUFFER_TANGENT], 3, GL_FLOAT, 0, 0, 0);	
	}
	
	// Update attribute values.
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[IMODEL_BUFFER_ELEMENTS]);
		
	glBindBuffer(GL_ARRAY_BUFFER,0);
	
#ifdef GL_ES_VERSION_2_0
    glBindVertexArrayOES(0);
#else
    glBindVertexArrayAPPLE(0);
#endif 
	
	
}

-(void)unmapBuffers {
	int i;
	for (i=0; i<IMODEL_NUM_BUFFERS; i++) {
		attribMapping[i] = -1;
	}
}

-(void)bindVertexArray {
#ifdef GL_ES_VERSION_2_0
    glBindVertexArrayOES(vao);
#else
    glBindVertexArrayAPPLE(vao);
#endif
}

-(void)unbindVertexArray {
	glBindBuffer(GL_ARRAY_BUFFER, 0);
#ifdef GL_ES_VERSION_2_0
    glBindVertexArrayOES(0);
#else
    glBindVertexArrayAPPLE(0);
#endif
}

-(void)draw {
	
	glDrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_SHORT, 0);
	rb_check_error_simple();
	
}

-(void)dealloc {

	[super dealloc];
	if (indices) {
		free(indices);
		indices = nil;
	}
	if (hTable) {
		hashtable_destroy(hTable, 1);
		hTable = nil;
	}
	
	if (vao) {
#ifdef GL_ES_VERSION_2_0
		glDeleteVertexArraysOES(1, &vao);
#else
		glDeleteVertexArraysAPPLE(1, &vao);
#endif

		vao = 0;
	}
	
	int i;
	for (i=0; i<IMODEL_NUM_BUFFERS; i++) {
		if ( buffers[i] ) {
			glDeleteBuffers(1, &buffers[i]);
			buffers[i] = 0;
		}
	}
	
	
}


@end
